﻿using System.Collections.Generic;
using System.Linq;
using WI.CandidateManager.Common.Data.Entities;

namespace WI.CandidateManager.Business.Objects
{
    using System;
    using System.IO;
    using System.Runtime.Serialization.Formatters.Binary;
    using WI.CandidateManager.Common.Data.Enums;

    /// <summary>
    /// Logic for working with
    /// </summary>
    public class CoachBO : UserBaseBO
    {
        #region Profile logic

        /// <summary>
        /// Creates new coach in database.
        /// </summary>
        /// <param name="coach">
        /// The coach.
        /// </param>
        public void CreateCoach(Coach coach)
        {
            coach = (Coach)PrepareUser(coach);
            coach.Role = UserRole.Coach;

            DataContext.CoachSet.Add(coach);
            DataContext.SaveChanges();
        }

        /// <summary>
        /// Gets coach by ID.
        /// </summary>
        /// <param name="id">
        /// The ID.
        /// </param>
        /// <returns>
        /// The WI.CandidateManager.Common.Data.Entities.Coach.
        /// </returns>
        public Coach GetCoachById(long id)
        {
            return DataContext.CoachSet.Single(a => a.Id == id);
        }

        /// <summary>
        /// Gets a list of coaches.
        /// </summary>
        /// <returns>
        /// The System.Collections.Generic.List`1[T -&gt; WI.CandidateManager.Common.Data.Entities.Coach].
        /// </returns>
        public List<Coach> GetCoachesList()
        {
            return DataContext.CoachSet.Where(a => a.State == State.Active).ToList();
        }

        /// <summary>
        /// Updates coach record on specified in entity ID;
        /// </summary>
        /// <param name="coach">
        /// The coach.
        /// </param>
        public void UpdateCoach(Coach coach)
        {
            var oldCoach = DataContext.CoachSet.Single(a => a.Id == coach.Id);

            oldCoach.FullName = coach.FullName;
            oldCoach.ShortName = coach.ShortName;
            oldCoach.Email = coach.Email;
            oldCoach.Phone = coach.Phone;
            oldCoach.Gender = coach.Gender;

            DataContext.SaveChanges();
        }

        /// <summary>
        /// Get user from session.
        /// </summary>
        /// <returns>User object</returns>
        public Coach DisplayCoachProfile()
        {
            try
            {
                var coach = DataContext.CoachSet.Single(u => u.Id == Session.User.Id);
                Logger.Info(String.Format("Try to get object of current coach(UserId={0}) from DB", Session.User.Id));
                return coach;
            }
            catch (Exception ex)
            {
                Logger.Error(String.Format("Unable to get object of current coach(UserId={0}) from DB", Session.User.Id), ex);
                return null;
            }
        }

        /// <summary>
        /// Get user object for changing.
        /// </summary>
        /// <param name="id">user id</param>
        /// <returns>Object of Coach</returns>
        public Coach GetCoachProfile(long UserId)
        {
            try
            {
                var coach = DataContext.CoachSet.Single(u => u.Id == UserId);
                Logger.Info(String.Format("Try to get object of coach by UserId={0} from DB", UserId));
                return coach;
            }
            catch (Exception ex)
            {
                Logger.Error(String.Format("Unable to get object of coach by UserId={0} from DB", UserId), ex);
                return null;
            }
        }

        /// <summary>
        /// Change user object.
        /// </summary>
        /// <param name="id">user id.</param>
        /// <param name="coach">objext of user.</param>
        /// <returns></returns>
        public bool ChangeCoachProfile(long UserId, Coach coach)
        {
            try
            {
                var curCoach = DataContext.CoachSet.Single(u => u.Id == UserId);

                curCoach.FullName = coach.FullName;
                curCoach.ShortName = coach.ShortName;
                curCoach.Email = coach.Email;
                curCoach.Phone = coach.Phone;
                curCoach.Gender = coach.Gender;

                DataContext.Entry(curCoach).State = System.Data.EntityState.Modified;
                DataContext.SaveChanges();
                Logger.Info(String.Format("Try to change coach profile by UserId={0} from DB", UserId));
                return true;
            }
            catch (Exception ex)
            {
                Logger.Error(String.Format("Unable to change coach profile by UserId={0} from DB", UserId), ex);
                return false;
            }
        }

        #endregion

        #region ManageGroups

        /// <summary>
        /// Gets the groups.
        /// </summary>
        /// <returns></returns>
        public List<StudyGroup> GetGroups()
        {
            try
            {
                var coach = DataContext.CoachSet.Single(c => c.Id == Session.User.Id);
                //TODO: Fix Couch property
                var groups = DataContext.StudyGroupSet.Where(g => g.Couch.Any(c => c.Id == coach.Id) && g.State == State.Active).ToList();
                Logger.Info(String.Format("Try to get list of studygroups for coach (UserId={0}) from DB", Session.User.Id));
                return groups;
            }
            catch (Exception ex)
            {
                Logger.Error("Unable to get list of studygroups for coach from DB", ex);
                return new List<StudyGroup>();
            }
        }

        /// <summary>
        /// Saves the group.
        /// </summary>
        /// <param name="group">The group.</param>
        /// <returns></returns>
        public bool SaveGroup(StudyGroup group)
        {
            try
            {
                Logger.Info("Try to save group");

                var coach = DataContext.CoachSet.Single(c => c.Id == Session.User.Id);
                group.State = Common.Data.Enums.State.Active;
                group.Couch.Add(coach);
                return new GroupBO().SaveGroup(group); ;
            }
            catch (Exception ex)
            {
                Logger.Error("Can't added group", ex);
                return false;
            }
        }

        public List<Candidate> GetCoachesCandidates()
        {
            try
            {
                var coach = DataContext.CoachSet.Single(c => c.Id == Session.User.Id);
                var candidates = DataContext.CandidateSet.Where(c => c.StudyGroup.Any(g => g.Couch.Any(u => u.Id == coach.Id)) && c.State == State.Active).ToList<Candidate>();
                Logger.Info(String.Format("Try to get list of coach's candidates from DB"));
                return candidates;
            }
            catch (Exception ex)
            {
                Logger.Error("Can't get list of coach's candidates from DB", ex);
                return new List<Candidate>();
            }
        }

        public bool AssignTaskToGroup(long TaskId, long GroupId)
        {
            try
            {
                var coach = DataContext.CoachSet.Single(c => c.Id == Session.User.Id);
                var group = DataContext.StudyGroupSet.Single(g => g.Id == GroupId && g.Couch.Any(c => c.Id == coach.Id));
                var task = DataContext.TaskBaseSet.Single(t => t.Id == TaskId);

                if (coach.Role == UserRole.Coach)
                {
                    foreach (var candidate in group.Candidate)
                    {
                        AssignTaskToCandidate(task.Id, candidate.Id);
                    }

                    DataContext.SaveChanges();
                    Logger.Info("Try to assing a task to group");
                }
                return true;
            }
            catch (Exception ex)
            {
                Logger.Error("Can't assing a task to group", ex);
                return false;
            }
        }

        public bool AssignTaskToCandidate(long TaskId, long CandidateId)
        {
            try
            {
                var coach = DataContext.CoachSet.Single(c => c.Id == Session.User.Id);
                var candidate = DataContext.CandidateSet.Single(c => c.Id == CandidateId 
                    && c.StudyGroup.Any(g => g.Couch.Any(co => co.Id == coach.Id)));

                var task = DataContext.TaskBaseSet.Single(t => t.Id == TaskId);

                if (coach.Role == UserRole.Coach)
                {
                    MakeResultCopy(task, candidate);

                    DataContext.SaveChanges();
                    Logger.Info("Try to assing a task to candidate");
                }
                return true;
            }
            catch (Exception ex)
            {
                Logger.Error("Can't assing a task to candidate", ex);
                return false;
            }
        }

        private void MakeResultCopy(TaskBase task, Candidate candidate)
        {
            TaskResult result = new TaskResult();
            TaskMark mark = new TaskMark();

            if (task is Test)
            {
                foreach (var question in (task as Test).QuestionBase)
                {
                    foreach (var answer in question.AnswerBase.ToList())
                    {
                        if (answer is SingleAnswer)
                        {
                            SingleAnswer newSingleAnswer = new SingleAnswer()
                            {
                                State = Common.Data.Enums.State.Active,
                                Text = answer.Text,
                                IsCorrect = answer.IsCorrect,
                                QuestionBase = answer.QuestionBase
                            };

                            result.AnswerBase.Add(newSingleAnswer);
                        }
                        else if (answer is FreeTextAnswer)
                        {
                            FreeTextAnswer newFreeTextAnswer = new FreeTextAnswer()
                            {
                                State = Common.Data.Enums.State.Active,
                                Text = answer.Text,
                                IsCorrect = answer.IsCorrect,
                                QuestionBase = answer.QuestionBase
                            };

                            result.AnswerBase.Add(newFreeTextAnswer);
                        }
                    }
                }
            }

            result.TaskBase = task;
            result.Status = TaskState.New;
            result.MarkBase.Add(mark);

            candidate.TaskBase.Add(task);
            candidate.ResultBase.Add(result);
        }

        public bool DeassingTaskToCandidate(long TaskId, long CandidateId)
        {
            try
            {
                var coach = DataContext.CoachSet.Single(c => c.Id == Session.User.Id);
                var candidate = DataContext.CandidateSet.Single(c => c.Id == CandidateId && c.StudyGroup.Any(g => g.Couch.Any(co => co.Id == coach.Id)));
                var task = DataContext.TaskBaseSet.Single(t => t.Id == TaskId && t.Candidate.Any(c => c.Id == candidate.Id));
                var result = DataContext.ResultBaseSet.Single(r => r.Candidate.Id == candidate.Id && r.TaskBase.Id == task.Id && r.State == State.Active);

                if (coach.Role != UserRole.Coach && !candidate.StudyGroup.Any(g => g.Couch.Any(c => c.Id == coach.Id)))
                {
                    throw new Exception("Unauthorized access.");
                }

                candidate.TaskBase.Remove(task);
                result.State = State.Deleted;
                DataContext.SaveChanges();
                Logger.Info("Try to deassing a task to candidate");

                return true;
            }
            catch (Exception ex)
            {
                Logger.Error("Can't deassing a task to candidate", ex);
                return false;
            }
        }

        #endregion

        #region ManageSubjects

        /// <summary>
        /// Gets the subjects.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Subject> GetSubjects()
        {
            try
            {
                Logger.Info("Try to get all coach subjects");
                var subjects = DataContext.SubjectSet.Where(s =>
                    s.CoachId == Session.User.Id &&
                    s.State == State.Active).ToList();
                return subjects;
            }
            catch (Exception ex)
            {
                Logger.Error("Can't get subjects", ex);
                return new List<Subject>();
            }
        }

        #endregion
    }
}
